package com.abel.redis;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.params.SetParams;
import java.util.Collections;

/**
 * @author heaveneleven
 * @date 2019/1/29
 */
public class RedisLock implements Lock{

    private Jedis jedis;
    /**
     * 加锁成功返回
     */
    private static final String LOCK_SUCCESS = "OK";
    /**
     * 释放锁成功返回
     */
    private static final Long UNLOCK_SUCCESS = 1L;
    /**
     * redis过期时间
     * 单位:秒
     */
    private int expire;
    /**
     * 获取锁超时时间
     * 单位:毫秒
     */
    private long timeout = 10000000;
    /**
     * 默认客户端标识
     * 在不区分客户端时使用
     */
    private static final String DEFAULT_OWNER = "heaveneleven";

    public static class Builder{
        private String ip;
        private int port;
        private int expire = 100;
        public Builder ip(String ip){
            this.ip = ip;
            return this;
        }
        public Builder port(int port){
            this.port = port;
            return this;
        }
        public Builder expire(int expire){
            this.expire = expire;
            return this;
        }
        public RedisLock build(){
            Jedis jedis = new Jedis(ip, port);
            if(jedis == null || !"PONG".equals(jedis.ping())){
                throw new RuntimeException("Initialize Redis Client Failed");
            }
            RedisLock redisLock = new RedisLock();
            redisLock.jedis = jedis;
            redisLock.expire = expire;
            return redisLock;
        }
    }

    @Override
    public boolean lock(String key, String owner, long timeout) {
        return this.trylock(key, owner, timeout);
    }

    @Override
    public boolean lock(String key, String owner){
        return this.trylock(key, owner, timeout);
    }

    @Override
    public boolean lock(String key) {
        return this.trylock(key, DEFAULT_OWNER, timeout);
    }

    @Override
    public boolean trylock(String key, String owner, long timeout) {
        SetParams setParams = new SetParams();
        setParams.nx();
        setParams.ex(expire);
        while(timeout > 0){
            long currentTime = System.currentTimeMillis();
            String result = jedis.set(key, owner, setParams);
            if(LOCK_SUCCESS.equals(result)){
                System.out.println(String.format("lock '%s' by '%s' successfully", key, owner));
                return true;
            }
            try{
                Thread.sleep(10);
            }catch (InterruptedException e){
                //do nothing
            }
            timeout -= System.currentTimeMillis() - currentTime;
        }
        return false;
    }


    @Override
    public boolean unlock(String key, String owner){
        String luaScript = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        Object result = jedis.eval(luaScript, Collections.singletonList(key), Collections.singletonList(owner));
        if(UNLOCK_SUCCESS.equals(result)){
            System.out.println(String.format("unlock '%s' by '%s' successfully", key, owner));
            return true;
        }
        return false;
    }

    @Override
    public boolean unlock(String key) {
        return this.unlock(key, DEFAULT_OWNER);
    }

}
